#pragma once 
/*这个是对elasticlient API接口的二次封装*/

#include <iostream>
#include <vector>
#include <string>
#include <elasticlient/client.h>
#include <cpr/cpr.h>
#include <json/json.h>
#include "logger.hpp"

namespace ChatSystem
{

bool Serialize(const Json::Value& val, std::string& dst)
{
    //先定义Json::StreamWriter 工厂类 Json::StreamWriterBuilder
    Json::StreamWriterBuilder swb;
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
    //通过Json::StreamWriter中的writer接口进行序列化
    std::stringstream ss;
    int ret = sw->write(val, &ss);
    if(ret != 0)
    {
        LOG_ERROR("Json反序列化失败! ");
        return false;
    }
    dst = ss.str();
    return true;
}
bool UnSerialize(const std::string& src, Json::Value& val)
{
    //先定义Json::CharReader 工厂类 Json::CharReaderBuilder
    Json::CharReaderBuilder crb;
    std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
    //通过Json::CharReader中的parse接口进行反序列化
    std::string err;
    bool ret = cr->parse(src.c_str(), src.c_str() + src.size(), &val, &err);
    if(ret == false)
    {
        LOG_ERROR("json反序列化失败: {}" , err);
        return false;
    }
    return true;
}
}

namespace ChatSystem
{
    //ES的索引库创建的封装
    class ESIndex
    {
    public:
        ESIndex(std::shared_ptr<elasticlient::Client>& client, 
            const std::string& name, const std::string& type = "_doc", 
            const std::string& index_id = "default_index_id")
        :_name(name), _type(type), _index_id(index_id), _client(client)
        {   
            //在创建ES索引库的时候，settings字段是固定不变的，我们直接初始化好，不用用户自己初始化
            Json::Value analysis;
            Json::Value analyzer;
            Json::Value ik;
            Json::Value tokenizer;
            tokenizer["tokenizer"] = "ik_max_word";
            ik["ik"] = tokenizer;
            analyzer["analyzer"] = ik;
            analysis["analysis"] = analyzer;
            _index["settings"] = analysis;

            std::string str;
            Serialize(_index, str);
            LOG_DEBUG("固定字段的初始化：{}", str);
        }
    
        //往索引库中追加对应的字段
        ESIndex& append(const std::string& key,     //字段的名称
            const std::string& type = "text",   //字段的类型
            const std::string& analyzer = "ik_max_word", //字段的分词器
            bool enabled = true)    //该字段是否建立映射，即是否可以被搜索
            {
                Json::Value fields;
                fields["type"] = type;
                fields["analyzer"] = analyzer;
                if(enabled == false)//在ES的索引库中enabled默认是true，当要设置为false映射的时候才赋值
                {
                    fields["enabled"] = enabled;
                }
                _properties[key] = fields;
                return *this;
            }
        //构建完整的索引库创建字符串，再向ES服务器发起请求创建索引库
        bool create()
        {
            Json::Value mappings;
            mappings["dynamic"] = true;
            mappings["properties"] = _properties;
            _index["mappings"] = mappings;

            //索引库创建字符串的序列化
            std::string body;
            bool ret = Serialize(_index, body);
            if(ret == false)
            {
                LOG_ERROR("创建索引库字符串序列化失败! ");
                return false;
            }
            LOG_INFO("创建索引请求正文：[{}]", body);
            //向ES服务器发起创建索引库的请求
            try
            {
                auto rsp = _client->index(_name, _type, _index_id, body);
                //判断索引库是否创建成功
                if(rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("创建ES索引 {} 失败, 响应状态码异常：{} ", _name, rsp.status_code);
                    return false;
                }
            }
            catch(std::exception& e)
            {
                LOG_ERROR("创建ES索引 {} 失败： {} ", _name, e.what());
                return false;
            }
            return true;
        }
    private:
        std::string _name;  //索引库的名称
        std::string _type;  //索引库的类型
        const std::string _index_id = "default_index_id";   //索引库的ID
        Json::Value _properties;
        Json::Value _index;
        std::shared_ptr<elasticlient::Client> _client;  //向ES服务器发起请求的客户端
    };

    //数据新增的封装
    class ESInsert
    {
    public:
        ESInsert(std::shared_ptr<elasticlient::Client>& client, const std::string& name, const std::string& type = "_doc")
        :_name(name), _type(type), _client(client)
        {}

        //往字段中添加数据；由于项目中val的类型不只有string类型，还有其他类型所以是模板参数
        template<class T>
        ESInsert& append(const std::string& key, const T& val)
        {
            _item[key] = val;
            return *this;
        }

        //将数据插入到索引库中
        bool insert(const std::string& id = "")
        {
            //往索引库中新增数据的字符串的序列化
            std::string body;
            bool ret = Serialize(_item, body);
            if(ret == false)
            {
                LOG_ERROR("新增数据字符串序列化失败! ");
                return false;
            }
            LOG_INFO("新增数据请求正文：[{}]", body);
            //向ES服务器发起向索引库中新增单条数据的请求
            try
            {
                //创建索引库和往索引库中新增数据都是index
                auto rsp = _client->index(_name, _type, id, body);
                //判断新增数据是否创建成功
                if(rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("新增数据 {} 失败, 响应状态码异常：{} ", _name, rsp.status_code);
                    return false;
                }
            }
            catch(std::exception& e)
            {
                LOG_ERROR("新增数据 {} 失败： {} ", body, e.what());
                return false;
            }
            return true;
        }
    private:
        std::string _name;  //索引库的名称
        std::string _type;  //索引库的类型
        Json::Value _item;
        std::shared_ptr<elasticlient::Client> _client;  //向ES服务器发起请求的客户端
    };

    //数据删除的封装
    class ESRemove
    {
    public:
        ESRemove(std::shared_ptr<elasticlient::Client>& client, const std::string& name, const std::string& type = "_doc")
        :_name(name), _type(type), _client(client)
        {}

        //从ES的索引库中删除数据的接口
        bool remove(const std::string& id)
        {
            //向ES服务器发起从索引库中删除数据的请求
            try
            {
                auto rsp = _client->remove(_name, _type, id);
                //判断删除数据是否创建成功
                if(rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("删除数据 {} 失败, 响应状态码异常：{} ", _name, rsp.status_code);
                    return false;
                }
            }
            catch(std::exception& e)
            {
                LOG_ERROR("删除数据 {} 失败： {} ", id, e.what());
                return false;
            }
            return true;
        }
    private:
        std::string _name;  //索引库的名称
        std::string _type;  //索引库的类型
        std::shared_ptr<elasticlient::Client> _client;  //向ES服务器发起请求的客户端
    };

    //查询数据的封装
    class ESSearch
    {
    public:
        ESSearch(std::shared_ptr<elasticlient::Client>& client, const std::string& name, const std::string& type = "_doc")
        :_name(name), _type(type), _client(client)
        {}

        //往必须不遵循条件中添加字段---terms是精确匹配，两者内容完全一致的时候才会匹配成功；由于项目中val的类型只有string，所以这里val是string
        ESSearch& append_must_not_terms(const std::string& key, const std::vector<std::string>& vals)
        {
            Json::Value fields;
            for(auto& val: vals)
            {
                fields[key].append(val);
            }
            Json::Value terms;
            terms["terms"] = fields;
            _must_not.append(terms);
            
            return *this;
        }

        //往应该遵循条件中添加字段---match是分词匹配，只要有一个分词能够匹配上就可以被查询到；由于项目中val的类型只有string，所以这里val是string
        ESSearch& append_should_match(const std::string& key, const std::string& val)
        {
            Json::Value field;
            field[key] = val;
            Json::Value match;
            match["match"] = field;
            _should.append(match);

            return *this;
        }

        //往必须遵循的条件中添加字段---must是必须遵循的条件，item 和 match 全部匹配上的时候才可以
        ESSearch& append_must_terms(const std::string& key, const std::string& val)
        {
            Json::Value fields;
            fields[key] = val;
            Json::Value terms;
            terms["term"] = fields;
            _must.append(terms);
            
            return *this;
        }
        //往必须遵循的条件中添加字段---must是必须遵循的条件，item 和 match 全部匹配上的时候才可以
        ESSearch& append_must_match(const std::string& key, const std::string& val)
        {
            Json::Value fields;
            fields[key] = val;
            Json::Value matchs;
            matchs["match"] = fields;
            _must.append(matchs);
            
            return *this;
        }

        //从ES的索引库中查询数据的接口，返回值是一个Json::Value类型
        Json::Value search()
        {
            Json::Value request_bool;
            if(_must_not.empty() == false)
            {
                request_bool["must_not"] = _must_not;
            }
            if(_should.empty() == false)
            {
                request_bool["should"] = _should;
            }
            if(_must.empty() == false)
            {
                request_bool["must"] = _must;
            }

            Json::Value query;
            query["bool"] = request_bool;
            _search["query"] = query;

            //查询索引库中数据的字符串的序列化
            std::string body;
            bool ret = Serialize(_search, body);
            if(ret == false)
            {
                LOG_ERROR("检索数据字符串序列化失败! ");
                return false;
            }
            LOG_INFO("查询数据请求正文：[{}]", body);
            //向ES服务器发起从索引库中查询数据的请求
            cpr::Response rsp;
            try
            {
                rsp = _client->search(_name, _type, body);
                //判断查询数据是否成功
                if(rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("检索数据 {} 失败, 响应状态码异常：{} ", _name, rsp.status_code);
                    return Json::Value();
                }
            }
            catch(std::exception& e)
            {
                LOG_ERROR("检索数据 {} 失败： {} ", body, e.what());
                return Json::Value();
            }
            LOG_DEBUG("检索响应正文：[{}]", rsp.text);
            //对响应正文进行反序列化
            Json::Value json_res;
            ret = UnSerialize(rsp.text, json_res);
            if(ret == false)
            {
                LOG_ERROR("检索数据 {} 的时候反序列化失败", rsp.text);
            }
            return json_res["hits"]["hits"];
        }
    private:
        std::string _name;  //索引库的名称
        std::string _type;  //索引库的类型
        Json::Value _must_not;  //必须不遵循的条件
        Json::Value _should;    //应该遵循的条件
        Json::Value _must;      //必须遵循的条件
        Json::Value _search;    //查询数据的json格式字符串
        std::shared_ptr<elasticlient::Client> _client;  //向ES服务器发起请求的客户端
    };
}